// 逻辑尽可能精简
function parse(template) {
  // 1.语义拆分 templateStr 拆成数组（语义）
  const tokens = tokenizer(template);
  // console.log('tokens', tokens);

  // 2. 数组 => 树， 数组变成树形结构，实现 ast
  let cur = 0;
  let ast = {
    type: 'root',
    props: [],
    children: [] // 子元素
  }
  while (cur < tokens.length) {
    ast.children.push(walk())
  }

  return ast;

  function walk() {
    let token = tokens[cur];
    if (token.type === 'tagstart') {
      let node = { // 开始标签，需要新建一个node
        type: 'element',
        tag: token.val,
        props: [],
        children: []
      }
      token = tokens[++cur];

      //  往下走一直到 tagend 或者是别的 tagstart，这之中都是自己的内容
      while (token.type !== 'tagend') {
        if (token.type === 'props') {
          node.props.push(walk());
        } else {
          node.children.push(walk());
        }

        token = tokens[cur];
      }
      cur++;

      return node;
    }

    if (token.type === 'tagend') {
      cur++;
    }

    if (token.type === 'text') {
      cur++;
      return token;
    }

    if (token.type === 'props') {
      cur++;
      // id = xxx
      const [key, val] = token.val.split('=');
      return {
        key,
        val
      }
    }
  }
}

function tokenizer(input) {
  // 期望返回一个数组，类似于下面
  // {{value: 'div', type: 'tagstart'}, {type: props}}
  let tokens = [];
  let type = '';
  let val = '';
  for (let i = 0; i < input.length; i++) {
    let ch = input[i];
    if (ch === '<') {
      push(); // <开头的字符，认为是语义更换，之前收集的内容val push到token里面去 
      if (input[i + 1] === '/') {
        type = 'tagend'; // </ 结束标签
      } else {
        type = 'tagstart'; // <div 开始标签
      }
    }

    if (ch === '>') {
      // 标签结束
      push();
      type = 'text';
      continue;
    } else if (/[\s]/.test(ch)) {
      // 如果空格匹配到
      push();
      type = 'props';
      continue;
    }

    val += ch;
  }

  function push() {
    if (val) {
      if (type === 'tagstart') val = val.slice(1); // 过滤 <
      if (type === 'tagend') val = val.slice(2); // 过滤 </
      tokens.push({
        type,
        val
      })

      val = '';
    }
  }

  return tokens;
}

function transform(ast) {
  // 给通用的 ast 加上 vue 的语法标识
  // 包括 vue 特定的 template 语法，包括所谓的静态标记
  let context = {
    helpers: new Set(['openBlock', 'createVnode']) // 用到的工具函数
  }
  // 属性结构，递归比较合理
  traverse(ast, context);
  ast.helpers = context.helpers;
}

function traverse(ast, context) {
  switch (ast.type) {
    case 'root':
      context.helpers.add('createBlock');
    case 'element':
      // element 就是元素，div或者html在这个里面
      // props 在这个里面
      // 需要做两件事情
      // 1. vue 语法要标记出来
      // 2. class、props、event 这几个如果是静态的，需要标记出来，方便后续 vdom diff 的时候略过
      ast.children.forEach(node => {
        traverse(node, context)
      })
      // 正常是二进制位运算，但是我们先整个对象好理解
      ast.flag = {
        props: false,
        class: false,
        event: false
      } // 默认都静态的
      ast.props = ast.props.map(prop => {
        const {
          key,
          val
        } = prop;
        if (key[0] === '@') {
          // 事件
          ast.flag.event = true; // 后续做节点对比的时候，需要对事件进行 diff，先 remove，再 addEventListener
          return {
            key: 'on' + key[1].toUpperCase() + key.slice(2), // @click => onclick
            val
          }
        }

        if (key[0] === ':') {
          // 动态属性
          ast.flag.props = true;
          return {
            key: key.slice(1),
            val
          }
        }

        if (key.startsWith === 'v-') {
          // 先略过，可以自己扩展
        }

        // 如果以上都没有就是静态的
        return {
          ...prop,
          static: true
        }
      })
    case 'text':
      // 文本我们需要知道{{}}，有这个就是动态，没有就是静态
      let re = /\{\{(.*)\}\}/g
      if (re.test(ast.val)) {
        ast.static = false;
        context.helpers.add('toDisplayString');
        ast.val = ast.val.replace(re, function (s0, s1) {
          return s1;
        })
      } else {
        ast.static = true; // 标记为true，后续vue进行dom diff的时候直接略过
      }
  }
}

function generate(ast) {
  const {
    helpers
  } = ast;

  let code = `
  import {${[...helpers].map(v => v + ' as _' + v)}} from 'vue'\n
  export function render(_ctx, _cache){
    return (_openBlock(), ${ast.children.map(node => walk(node))})
  }
  `

  function walk(node) {
    switch (node.type) {
      case 'element':
        let {
          flag
        } = node;
        let props = '{' + node.props.reduce((ret, p) => {
          console.log('p', flag, p);
          if (p.key === 'class') {
            if (flag.class) {
              ret.push(p.key + ':_ctx.' + p.val.replace(/['"]/g, ''));
            } else {
              ret.push(p.key + ':' + p.val);
            }
          } else {
            if (flag.props) {
              // 属性是动态的
              ret.push(p.key + ':_ctx.' + p.val.replace(/['"]/g, ''));
            } else {
              ret.push(p.key + ':' + p.val);
            }
          }
          return ret;
        }, []).join(',') + '}'
        return `_createVnode("${node.tag}", ${props}, 
          [${node.children.map(n => walk(n))}]),
          ${JSON.stringify(flag)}
        `;
      case 'text':
        if (node.static) {
          return '"' + node.val + '"';
        } else {
          return `__toDisplayString(_ctx.${node.val})`;
        }
    }
  }

  return code;
}

function compile(template) {
  let ast = parse(template);
  transform(ast); //代码标记和优化，静态标记，比如v-开头的特殊语法
  console.log('ast', JSON.stringify(ast, null, 2));

  const code = generate(ast);
  console.log('code', code);
  return code;
}

const templateStr = `<div id="app">
<h1 @click="add" class="item" :id="count">{{count}}</h1>
<p>今天摸鱼真快乐</p>
</div>`

let code = compile(templateStr)
